/**
 * DataLib
 * Loader manager class
 *
 * @author		Andre Anaya
 */
package com.andreanaya.net
{
	import com.andreanaya.events.DataLibEvent;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.net.URLRequest;
	import flash.net.URLVariables;
	import flash.system.ApplicationDomain;
	import flash.system.LoaderContext;
	import flash.utils.Dictionary;
	
	/**
	 * Dispatched when the queue load is complete.
	 * 
	 * @eventType com.andreanaya.events.DataLibEvent
	 * 
	 * @see com.andreanaya.events.DataLibEvent#COMPLETE
	 */
	[Event(name="complete", type="com.andreanaya.events.DataLibEvent")]
	/**
	 * Dispatched when the queue load is in progress.
	 * 
	 * @eventType com.andreanaya.events.DataLibEvent
	 * 
	 * @see com.andreanaya.events.DataLibEvent#PROGRESS
	 */
	[Event(name="progress", type="com.andreanaya.events.DataLibEvent")]
	
	
	/**
	 * DataLib
	 * <p>Loader manager class</p>
	 *
	 * @example The following code show how to load an image with DataLib:
	 * <listing version="3.0">
	 * 
	 * var dataLib:DataLib = new DataLib();
	 * dataLib.addItem("image.jpg", DataLib.TYPE_IMAGE);
	 * 
	 * dataLib.addEventListener(DataLibEvent.COMPLETE, onLoadComplete);
	 * 
	 * dataLib.load();
	 * 
	 * function onLoadComplete(e:DataLibEvent):void
	 * {
	 * 		var image:Bitmap = DataLib.getData("image.jpg");
	 * 		addChild(image);
	 * }</listing> 
	 */
	public class DataLib extends EventDispatcher
	{
		/**
		 * The <code>DataLib.TYPE_BINARY</code> constant defines a binary file to be loaded.
		 */
		public static const TYPE_BINARY:String = "binary";
		/**
		 * The <code>DataLib.TYPE_IMAGE</code> constant defines an image file to be loaded.
		 */
		public static const TYPE_IMAGE:String = "image";
		/**
		 * The <code>DataLib.TYPE_SWF</code> constant defines a swf file to be loaded.
		 */
		public static const TYPE_SWF:String = "swf";
		/**
		 * The <code>DataLib.TYPE_TEXT</code> constant defines a text file to be loaded.
		 */
		public static const TYPE_TEXT:String = "text";
				
		private static var _initied:Boolean;
		
		private static var _lib:Dictionary;
		
		private static var _loaders:Array;
		
		private static var _defaultLoaderContext:LoaderContext;
		
		private static var _downloads:uint = 0;
		private static var _maxDownloads:uint = 3;
		
		private static function init():void
		{
			if(_initied) return;
			
			_initied = true;
			
			_lib = new Dictionary(true);
			
			_loaders = new Array();
			
			_defaultLoaderContext = new LoaderContext();
			_defaultLoaderContext.applicationDomain = ApplicationDomain.currentDomain;
		}
		
		private static function formatRequest(request:*):URLRequest
		{
			if(request is URLRequest)
			{
				return request;
			}
			else if(request is String)
			{
				var url:String = request.split("?")[0];
				
				var urlRequest:URLRequest = new URLRequest();
				
				urlRequest.url = (" "+url).substr(1); //Bug!!!
				
				if(request.indexOf("?") > -1)
				{
					var variables:URLVariables = new URLVariables();
					
					variables.decode(request.substr(request.indexOf("?")+1));
					urlRequest.data = variables;
				}
				
				return urlRequest;
			}
			else
			{
				throw new Error("Invalid request.");
			}
		}
		/**
		 * @private 
		 */
		internal static function formatID(request:URLRequest):String
		{
			var id:String = request.url;
			
			if(request.data)
			{
				id += "?"+request.data.toString();
			}
			
			return id;
		}
		private static function setData(item:DataLibItem):void
		{
			if(_lib[formatID(item.request)] == undefined) _lib[formatID(item.request)] = item.data;
		}
		
		/** 
		 * Retrieve data from the library.
		 * 
		 * @param request The url path or URLRequest of the file.
		 * 
		 * @return The loaded data.
		 */
		public static function getData(request:*):*
		{
			return _lib[formatID(formatRequest(request))];
		}
		/** 
		 * Delete data from the library.
		 * 
		 * @param request The url path or URLRequest of the file.
		 */
		public static function deleteData(request:*):void
		{
			delete _lib[formatID(formatRequest(request))];
		}
		/** 
		 * Close all instances of DataLib.
		 * 
		 * @see #close()
		 */
		public static function closeAll():void
		{
			var i:uint;
			var t:uint = _loaders.length;
			
			for(i = 0; i<t; i++)
			{
				_loaders[i].close();
			}
		}
		/** 
		 * Clear all instances of DataLib.
		 * 
		 * @see #clear()
		 */
		public static function clearAll():void
		{
			var i:uint;
			var t:uint = _loaders.length;
			
			for(i = 0; i<t; i++)
			{
				_loaders[i].clear();
			}
		}
		/** 
		 * Stops all the load process, close and clear all instances of DataLib and delete all data from the library.
		 */
		public static function dispose():void
		{
			var i:uint;
			var t:uint = _loaders.length;
			
			for(i = 0; i<t; i++)
			{
				_loaders[i].close();
				_loaders[i].clear();
			}
			
			_loaders = null;
			_lib = null;
			_defaultLoaderContext = null;
			
			_maxDownloads = 3;
			
			_initied = false;
		}
		private static function loadNextItem():void
		{
			while(_loaders.length > 0 && _loaders[0].getNextItem() && _downloads<_maxDownloads)
			{
				_downloads++;
				
				_loaders.push(_loaders.shift());
				_loaders[0].getNextItem().load();
			}
		}
		/**
		 * Indicates how many files can be loaded at the same time.
		 * 
		 * @default 3
		 */
		public static function get maxDownloads():uint
		{
			return _maxDownloads;
		}
		public static function set maxDownloads(value:uint):void
		{
			_maxDownloads = value;
		} 
		
		private var _queue:Array;
		
		/** 
		 * Creates a new DataLib instance.
		 */
		public function DataLib()
		{
			init();
			
			_queue = new Array();
		}
		
		/** 
		 * Returns the load queue.
		 */
		public function get queue():Array
		{
			return _queue.concat();
		}
		
		/** 
		 * Adds an item to the load queue of the instance and returns that item.
		 * 
		 * @param request The url path or URLRequest of the file to be loaded.
		 * @param type The type of file to be loaded. Acceptable types of files are: <code>DataLib.TYPE_BINARY</code>, <code>DataLib.TYPE_IMAGE</code>, <code>DataLib.TYPE_SWF</code> and <code>DataLib.TYPE_TEXT</code>
		 * @param returnObject The object to be returned when the file load is complete.
		 * @param context A LoaderContext object. This object is used only for images and swf files. If the context parameter is not specified it will refers to the DataLib defaultLoaderContext.
		 * 
		 * @return A DataLibItem object in wich can be added DataLibEvent.COMPLETE and DataLibEvent.PROGRESS events.
		 * 
		 * @see DataLib#TYPE_BINARY
		 * @see DataLib#TYPE_IMAGE
		 * @see DataLib#TYPE_SWF
		 * @see DataLib#TYPE_TEXT
		 */
		public function addItem(request:*, type:String, returnObject:Object = null, context:LoaderContext = null):DataLibItem
		{
			return addItemAt(request, type, _queue.length, returnObject, context)
		}
		/** 
		 * Adds an item to the load queue of the instance and returns that item.
		 * 
		 * @param request The url path or URLRequest of the file to be loaded.
		 * @param type The type of file to be loaded. Acceptable types of files are: <code>DataLib.TYPE_BINARY</code>, <code>DataLib.TYPE_IMAGE</code>, <code>DataLib.TYPE_SWF</code> and <code>DataLib.TYPE_TEXT</code>
		 * @param index The index position to which the item is added.
		 * @param returnObject The object to be returned when the file load is complete.
		 * @param context A LoaderContext object. This object is used only for images and swf files. If the context parameter is not specified it will refers to the DataLib defaultLoaderContext.
		 * 
		 * @return A DataLibItem object in wich can be added DataLibEvent.COMPLETE and DataLibEvent.PROGRESS events. 
		 * 
		 * @see DataLib#TYPE_BINARY
		 * @see DataLib#TYPE_IMAGE
		 * @see DataLib#TYPE_SWF
		 * @see DataLib#TYPE_TEXT
		 */
		public function addItemAt(request:*, type:String, index:uint, returnObject:Object = null, context:LoaderContext = null):DataLibItem
		{
			var i:uint = _queue.length;
			
			var urlRequest:URLRequest = formatRequest(request);
			
			while(--i>-1)
			{
				if(formatID(urlRequest) == formatID(_queue[i].request)) return _queue[i];
			}
			
			var dataLibItem:DataLibItem = new DataLibItem(urlRequest, checkType(type), returnObject, (context)?context:_defaultLoaderContext);
			
			dataLibItem.addEventListener(IOErrorEvent.IO_ERROR, onError);
			dataLibItem.addEventListener(ProgressEvent.PROGRESS, onProgress);
			dataLibItem.addEventListener(Event.COMPLETE, onComplete);
			
			_queue.splice(index, 0, dataLibItem);
			
			return dataLibItem;
		}
		private function checkType(type:String):String
		{
			if(type != TYPE_BINARY && type != TYPE_IMAGE && type != TYPE_SWF && type != TYPE_TEXT)
			{
				throw new Error("Invalid type: ", type);
			}
			
			return type;
		}
		/** 
		 * Remove an item from the load queue.
		 * 
		 * @param request The url path or URLRequest of the file to be removed.
		 */
		public function removeItem(request:*):void
		{
			var i:uint = _queue.length;
			
			var url:String = formatRequest(request).url;
			
			while(--i>-1)
			{
				if(_queue[i].request.url == url)
				{
					_queue.splice(i, 1);
				}
			}
		}
		/** 
		 * Remove an item from the load queue at a especific position.
		 * 
		 * @param index The index where the item to be removed is placed.
		 */
		public function removeItemAt(index:uint):void
		{
			_queue.splice(index, 1);
		}
		/** 
		 * Delete an item from the library.
		 * 
		 * @param request The url path or URLRequest of the file to be deleted.
		 */
		public function deleteItem(request:*):void
		{
			deleteData(request);
		}
		/** 
		 * Retrieve an item from the load queue.
		 * 
		 * @param request The url path or URLRequest of the item.
		 * 
		 * @return A DataLibItem object in wich can be added DataLibEvent.COMPLETE and DataLibEvent.PROGRESS events.
		 */
		public function getItem(request:*):DataLibItem
		{
			var i:uint = _queue.length;
			
			var url:String = formatRequest(request).url;
			
			while(--i>-1)
			{
				if(_queue[i].request.url == url)
				{
					return _queue[i];
				}
			}
			
			return null
		}
		/** 
		 * Change the position of an item in the load queue.
		 * 
		 * @param request The url path or URLRequest of the item.
		 * @param priority The position where the item will be placed.
		 * 
		 * @return A DataLibItem object in wich can be added DataLibEvent.COMPLETE and DataLibEvent.PROGRESS events.
		 */
		public function setPriority(request:*, priority:uint):DataLibItem
		{
			var i:uint = _queue.length;
			
			var url:String = formatRequest(request).url;
			
			priority = Math.min(priority, _queue.length-1);
			
			while(--i>-1)
			{
				if(_queue[i].request.url == url)
				{
					var item:DataLibItem = _queue[i];
					
					_queue.splice(priority, 0, _queue.splice(i, 1)[0]);
					
					return item;
				}
			}
			
			return null
		}
		/** 
		 * Starts the loading process.
		 */
		public function load():void
		{
			if(_queue.length > 0)
			{
				var i:uint;
				var t:uint = _loaders.length;
				
				var chk:Boolean = true;
				
				for(i = 0; i<t; i++)
				{
					if(_loaders[i] == this) chk = false
				}
				
				if(chk) _loaders.push(this);
				
				loadNextItem();
			}
			else
			{
				dispatchEvent(new DataLibEvent(DataLibEvent.COMPLETE));
			}
		}
		/** 
		 * Stops the loading process.
		 */
		public function close():void
		{
			var i:uint;
			var t:uint = _queue.length;
			
			for(i = 0; i<t; i++)
			{
				if(_queue[i].initied && !_queue[i].loaded) _queue[i].close();
			}
			
			i = _loaders.length;
			
			while(--i>-1)
			{
				if(_loaders[i] == this)
				{
					_loaders.splice(i, 1);
					break;
				}
			}
		}
		/** 
		 * Remove all itens from the load queue.
		 */
		public function clear():void
		{
			_queue.splice(0, _queue.length);
		}
		private function getNextItem():DataLibItem
		{
			var i:uint;
			var t:uint = _queue.length;
			
			for(i = 0; i<t; i++)
			{
				if(!_queue[i].initied) return _queue[i];
			}
			
			return null;
		}
		private function nextItem():void
		{
			_downloads--;
			
			var i:uint;
			var t:uint = _queue.length;
			
			var chk:Boolean = true;
			
			for(i = 0; i<t; i++)
			{
				if(!_queue[i].loaded) chk = false
			}
			
			if(!getNextItem())
			{
				i = _loaders.length;
				
				while(--i>-1)
				{
					if(_loaders[i] == this)
					{
						_loaders.splice(i, 1);
						
						break
					}
				}
			}
			
			if(chk)
			{
				_queue.splice(0, _queue.length);
				
				dispatchEvent(new DataLibEvent(DataLibEvent.COMPLETE));
			}
			
			loadNextItem();
		}
		private function onError(e:IOErrorEvent):void{
			trace("DataLib ERROR\n\tURL not found: "+formatID(e.currentTarget.request));
			
			var i:uint = _queue.length;
			
			while(--i>-1)
			{
				if(!_queue[i] == e.currentTarget)
				{
					_queue.splice(i, 1);
					break
				} 
			}
			
			nextItem();
		}
		private function onProgress(e:ProgressEvent):void
		{
			e.currentTarget.dispatchEvent(new DataLibEvent(DataLibEvent.PROGRESS, false, false, {percent:e.bytesLoaded/e.bytesTotal, bytesLoaded:e.bytesLoaded, bytesTotal:e.bytesTotal, id:formatID(e.currentTarget.request)}));
			
			var i:uint;
			var t:uint = _queue.length;
			
			var bytesLoaded:Number = 0;
			var bytesTotal:Number = 0;
			
			for(i = 0; i<t; i++)
			{
				bytesLoaded += _queue[i].bytesLoaded/((_queue[i].bytesTotal > 0)?_queue[i].bytesTotal:1);
				bytesTotal ++;
			}
			
			dispatchEvent(new DataLibEvent(DataLibEvent.PROGRESS, false, false, {percent:bytesLoaded/bytesTotal, bytesLoaded:bytesLoaded, bytesTotal:bytesTotal}));
		}
		private function onComplete(e:Event):void
		{
			setData(e.currentTarget as DataLibItem);
			
			e.currentTarget.dispatchEvent(new DataLibEvent(DataLibEvent.COMPLETE, false, false, {data:e.currentTarget.data, id:formatID(e.currentTarget.request), returnObject:e.currentTarget.returnObject}));
			
			nextItem();
		}
	}
}